package ecustom.common;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSON;

import ecustom.common.util.Base64Util;
import ecustom.common.util.CustomUtil;
import ecustom.common.util.IOUtil;

/**
 * HTTP客户端。
 * @author 曹水涛
 */
public class HttpClient {

	private String url;
	private String encoding;
	private String method;
	private Map<String, String> headers;
	private Level level;

	private static final Logger log = Logger.getLogger(HttpClient.class);

	public String get() throws IOException {
		HttpURLConnection conn = openConnection();
		return read(conn);
	}

	/**
	 * <pre>
	 * 发送POST请求。
	 * 自动设置 Content-Type: application/x-www-form-urlencoded
	 * </pre>
	 * @param params
	 * @return
	 * @throws IOException
	 */
	public String post(Map<String, String> params) throws IOException {
		Set<String> paramsSet = params.keySet();
		StringBuffer sb = new StringBuffer();
		for (String name : paramsSet) {
			sb.append("&").append(name).append("=").append(params.get(name));
		}

		return post(sb.length() > 0 ? sb.substring(1) : null);
	}

	/**
	 * <pre>
	 * 发送JSON请求。
	 * 自动设置 Content-Type: application/json
	 * </pre>
	 * @param body	请求体，如果不是字符串会自动转JSON字符串
	 * @return
	 * @throws IOException
	 */
	public String postJson(Object body) throws IOException {
		if (CustomUtil.isBlank(body))
			throw new RuntimeException("postJson() body is blank");
		log.debug(getEncoding());
		setHeader("Content-Type", "application/json; charset=" + getEncoding());
		if (body instanceof String)
			return post(body.toString());

		return post(JSON.toJSONString(body));
	}

	public String post(String body) throws IOException {
		setMethod("POST");
		HttpURLConnection conn = openConnection();
		write(conn, body);
		return read(conn);
	}

	private HttpURLConnection openConnection() throws IOException {
		log.log(getLevel(), "openConnection() " + getMethod() + " " + getUrl());
		HttpURLConnection conn = (HttpURLConnection) new URL(getUrl()).openConnection();

		if ("POST".equals(getMethod())) {
			conn.setDoOutput(true);
			conn.setDoInput(true);
		}

		for (Entry<String, String> entry : getHeaders().entrySet()) {
			conn.setRequestProperty(entry.getKey(), entry.getValue());
		}

		conn.setRequestMethod(getMethod());
		return conn;
	}

	private void write(HttpURLConnection conn, String body) throws IOException {
		log.log(getLevel(), "write() body: " + body);
		DataOutputStream out = null;

		try {
			out = new DataOutputStream(conn.getOutputStream());
			out.write(body.getBytes(getEncoding()));
			out.flush();
		} finally {
			IOUtil.close(out);
		}
	}

	private String read(HttpURLConnection conn) throws IOException {
		int responseCode = conn.getResponseCode();
		if (HttpURLConnection.HTTP_OK != conn.getResponseCode()) {
			String resp = getResponse(conn.getErrorStream());
			log.error("read() responseStr: " + StringEscapeUtils.unescapeHtml(resp));
			throw new RuntimeException("read() responseCode: " + responseCode);
		}

		String resp = getResponse(conn.getInputStream());
		log.log(getLevel(), "read() body: " + CustomUtil.left(resp, 10000));
		return resp;
	}
	
	private String getResponse(InputStream is) throws IOException {
		StringBuilder sb = new StringBuilder();
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(is, getEncoding()));

			String readLine = new String();
			while((readLine = br.readLine()) != null) {
				sb.append(readLine).append("\n");
			}
		} finally {
			IOUtil.close(is);
			IOUtil.close(br);
		}
		return sb.toString();
	}

	/**
	 * 生成Basic Auth Header
	 * @param username
	 * @param password
	 * @return
	 */
	public static String getBasicAuth(String username, String password) {
		String auth = username + ":" + password;
		String encodedAuth = Base64Util.encode(auth.getBytes());
		String authHeader = "Basic " + encodedAuth;
		return authHeader;
	}

	/**
	 * 默认UTF-8
	 * @return
	 */
	private String getEncoding() {
		if (CustomUtil.isBlank(encoding))
			encoding = CustomUtil.getEncoding();
		return encoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * 调试环境下默认DEBUG，其他环境下默认为INFO。
	 * @return
	 */
	private Level getLevel() {
		if (level == null || log.isDebugEnabled())
			level = Level.DEBUG;

		return level;
	}

	public void setLevel(Level level) {
		this.level = level;
	}

	private String getUrl() {
		if (CustomUtil.isBlank(url))
			throw new RuntimeException("url is blank");

		return url;
	}

	public HttpClient setUrl(String url) {
		this.url = url;
		return this;
	}

	public HttpClient setUrl(StringBuilder url) {
		if (CustomUtil.isBlank(url))
			throw new RuntimeException("url is blank");
		this.url = url.toString();
		return this;
	}

	/**
	 * 默认发送GET请求。
	 * @return
	 */
	private String getMethod() {
		if (CustomUtil.isBlank(method))
			method = "GET";
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	private Map<String, String> getHeaders() {
		if (headers == null) {
			headers = new HashMap<String, String>();
			headers.put("Content-Type", "application/x-www-form-urlencoded; charset=" + getEncoding());		
			headers.put("Accept", "*/*");		
		}
		return headers;
	}

	public void setHeaders(Map<String, String> headers) {
		this.headers = headers;
	}

	public HttpClient setHeader(String name, String value) {
		getHeaders().put(name, value);
		return this;
	}
}